package center.wxp.log.core;

import center.wxp.log.annotation.ParamLog;
import center.wxp.log.annotation.ResultLog;
import center.wxp.log.constant.Level;
import center.wxp.log.properties.LogProperties;
import center.wxp.log.util.EmptyUtil;
import center.wxp.log.util.RemoteHostUtil;
import center.wxp.log.util.SnowflakeIdUtil;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * 打印log实现类
 *
 * @author wuxiaopeng
 * date 2020/3/12 10:09
 */
@Aspect
public class LogService {
    private LogProperties properties;

    public LogService() {

    }

    public LogService(LogProperties properties) {
        this.properties = properties;
        logger.info("日志配置属性:{}", properties.toString());
    }

    private static final Logger logger = LoggerFactory.getLogger(LogProperties.class);

    @Before("@annotation(center.wxp.log.annotation.ParamLog)")
    public void beforePrint(JoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //不需要打印参数类型
        if (interceptType(joinPoint, request)) {
            return;
        }
        //接口名称
        String apiName = getApiName(joinPoint, request);

        printReqParam(joinPoint, request, apiName, "");
    }

    @AfterReturning(value = "@annotation(center.wxp.log.annotation.ResultLog)", returning = "returnValue")
    public void afterPrint(JoinPoint joinPoint, Object returnValue) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //不需要打印参数类型
        if (interceptType(joinPoint, request)) {
            return;
        }
        //接口名称
        String apiName = getApiName(joinPoint, request);

        //打印返回参数
        printRespParam(apiName, "", returnValue);
    }


    /**
     * 调用controller包下的任意类的任意方法时均会调用此方法
     * execution: 切入点表达式
     */
    @Around(value = "@annotation(io.swagger.annotations.ApiOperation)")
    public Object aroundPrint(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnValue = joinPoint.proceed();
        if (properties.isEnabled_global_print()) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            //不需要打印参数类型
            if (interceptType(joinPoint, request)) {
                return returnValue;
            }
            //接口名称
            String apiName = getApiName(joinPoint, request);
            //打印id
            String printId = SnowflakeIdUtil.getSnowId();
            if ("BEFORE".equals(properties.getPosition()) || "ALL".equals(properties.getPosition())) {
                printReqParam(joinPoint, request, apiName, printId);
            }

            if ("AFTER".equals(properties.getPosition()) || "ALL".equals(properties.getPosition())) {
                //打印返回参数
                printRespParam(apiName, printId, returnValue);
            }
        }
        return returnValue;
    }

    private void printReqParam(JoinPoint joinPoint, HttpServletRequest request,
                               String apiName, String printId) throws Throwable {
        //远程主机ip
        String ipAddr = RemoteHostUtil.getRemoteHost(request);

        String url = request.getRequestURL().toString();
        //参数解析
        String reqParam = preHandle(joinPoint, request);
        StringBuffer printReqParm = new StringBuffer();
        printReqParm.append("【入参】业务名称:【{" + apiName + ":" + printId + "}】");
        printReqParm.append("\n" + "请求源IP:【{" + ipAddr + "}】, ");
        printReqParm.append("请求URL:【{" + url + "}】, ");
        printReqParm.append("请求参数:【{" + reqParam + "}】");
        print(properties.getLevel(), printReqParm.toString());
    }

    private void printRespParam(String apiName, String printId, Object result) {
        String respParam = EmptyUtil.isNotEmpty(result) ? JSON.toJSONString(result) : "";
        StringBuffer printRespParam = new StringBuffer();
        printRespParam.append("【出参】业务名称:【{" + apiName + ":" + printId + "}】");
        printRespParam.append("\n" + "参数:【{" + respParam + "}】");
        print(properties.getLevel(), printRespParam.toString());
    }

    /**
     * 打印信息
     *
     * @param level 日志级别
     * @param msg   输出信息
     */
    private void print(Level level, String msg) {
        switch (level) {
            case DEBUG:
                logger.debug(msg);
                break;
            case INFO:
                logger.info(msg);
                break;
            case WARN:
                logger.warn(msg);
                break;
            case ERROR:
                logger.error(msg);
                break;
            default:
        }
    }

    /**
     * 入参数据
     *
     * @param joinPoint
     * @param request
     * @return
     */
    private String preHandle(JoinPoint joinPoint, HttpServletRequest request) {
        String reqParam = "";
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Annotation[] annotations = targetMethod.getAnnotations();
        for (Annotation annotation : annotations) {
            //此处可以改成自定义的注解
            if (getRequestType(annotation)) {
                reqParam = JSON.toJSONString(request.getParameterMap());
                if ("{}".equals(reqParam)) {
                    BufferedReader br = null;
                    StringBuilder sb = new StringBuilder("");
                    try {
                        Object[] objects = joinPoint.getArgs();
                        reqParam = JSON.toJSONString(objects[0]);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                break;
            }
        }
        return reqParam;
    }

    /**
     * 拦截有效类型不需要打印参数(true:不需要打印，false:需要打印)
     *
     * @param joinPoint
     * @param request
     * @return
     */
    private Boolean interceptType(JoinPoint joinPoint, HttpServletRequest request) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Annotation[] annotations = targetMethod.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(ModelAttribute.class)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取接口名称
     *
     * @param joinPoint
     * @param request
     * @return
     */
    private String getApiName(JoinPoint joinPoint, HttpServletRequest request) {
        String apiName = "";
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Annotation[] annotations = targetMethod.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(ParamLog.class)) {
                //获取swagger属性值
                apiName = getAnnotationValue(annotation, "value").toString();
                if (EmptyUtil.isNotEmpty(apiName)) {
                    return apiName;
                }
            }
            if (annotation.annotationType().equals(ResultLog.class)) {
                //获取swagger属性值
                apiName = getAnnotationValue(annotation, "value").toString();
                if (EmptyUtil.isNotEmpty(apiName)) {
                    return apiName;
                }
            }
            if (annotation.annotationType().equals(ApiOperation.class)) {
                //获取swagger属性值
                apiName = getAnnotationValue(annotation, "value").toString();
                return apiName;
            }
        }
        return apiName;
    }

    /**
     * 获取该注解对象的属性值
     *
     * @param annotation
     * @param property
     * @return
     */
    public static Object getAnnotationValue(Annotation annotation, String property) {
        Object result = null;
        if (annotation != null) {
            //获取被代理的对象
            InvocationHandler invocation = Proxy.getInvocationHandler(annotation);
            Map map = (Map) getFieldValue(invocation, "memberValues");
            if (map != null) {
                result = map.get(property);
            }
        }
        return result;
    }

    public static <T> Object getFieldValue(T object, String property) {
        if (object != null && property != null) {
            Class<T> currClass = (Class<T>) object.getClass();
            try {
                Field field = currClass.getDeclaredField(property);
                field.setAccessible(true);
                return field.get(object);
            } catch (NoSuchFieldException e) {
                throw new IllegalArgumentException(currClass + " has no property: " + property);
            } catch (IllegalArgumentException e) {
                throw e;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private Boolean getRequestType(Annotation annotation) {
        Map<Class, Object> map = new HashMap<>();
        map.put(GetMapping.class, 1);
        map.put(PostMapping.class, 2);
        map.put(PutMapping.class, 3);
        map.put(DeleteMapping.class, 4);
        if (EmptyUtil.isEmpty(map.get(annotation.annotationType()))) {
            return false;
        }
        return true;
    }
}
